#include "ide.h"
#include "stdio_kernel.h"
#include "global.h"
#include "io.h"
#include "debug.h"
#include "sync.h"
#include "stdio.h"
#include "interrupt.h"
#include "list.h"
#include "thread.h"
#include "string.h"
#include "timer.h"

/* 定义硬盘各寄存器端口号 */
#define reg_data(channel)        (channel->port_base + 0)
#define reg_error(channel)       (channel->port_base + 1)
#define reg_features(channel)    (channel->port_base + 1)
#define reg_sec_cnt(channel)     (channel->port_base + 2)
#define reg_lba_l(channel)       (channel->port_base + 3)
#define reg_lba_m(channel)       (channel->port_base + 4)
#define reg_lba_h(channel)       (channel->port_base + 5)
#define reg_dev(channel)         (channel->port_base + 6)
#define reg_status(channel)      (channel->port_base + 7)
#define reg_cmd(channel)         (channel->port_base + 7)
#define reg_alt_status(channel)  (channel->port_base + 0x206)
#define reg_ctl(channel)         (channel->port_base + 0x206)

/* status 寄存器8 bit一些关键位 */
#define BIT_STAT_BSY    0x80    // 1000_0000h, 硬盘忙1, 不忙0
#define BIT_STAT_DRDY   0x40    // 0100_0000h, 设备就绪1, 等待指令
#define BIT_STAT_DRQ    0x8     // 0000_1000h, 数据传输准备好1, 随时可以输出

/* device寄存器8 bit一些关键位 */
// 0-3 位是LBA地址23-27位
#define BIT_DEV_MBS    0xa0 // 1010_0000h, 第7,5位固定1
#define BIT_DEV_LBA    0x40 // 0100_0000h, 寻址模式, LBA1, CHS0
#define BIT_DEV_DEV    0x10 // 0001_0000h, 第4位, 表0主盘/1从盘

/* command 寄存器8 bit*/
// 把命令写入此寄存器, 硬盘就开始相应操作
#define CMD_IDENTIFY   0xec    // 硬盘识别
#define CMD_READ_SECTOR    0x20   // 读扇区
#define CMD_WRITE_SECTOR   0x30   // 写扇区

#define max_lba ((80*1024*1024/512) - 1)    // 可读最大扇数
int32_t ext_lba_base = 0; // 用于记录总扩展分区的起始lba
uint8_t p_no = 0, l_no = 0;// 用于记录硬盘主分区和逻辑分区下标

/* 分区表项, 16Byte */
struct partition_table_entry {
    uint8_t bootable; // 活动分区标记, 0x80表活动分区, 可引导; 0表非活动分区, 不可引导
    uint8_t start_head;
    uint8_t start_sec;
    uint8_t start_chs;
    uint8_t fs_type;  // 文件系统类型ID, 0表不可识别, 1表FAT32, 等
    uint8_t end_head;
    uint8_t end_sec;
    uint8_t end_chs;
    uint32_t start_lba; // 本区起始扇区lba地址
    uint32_t sec_cnt;   // 本区扇区数
} __attribute__ ((packed));

/* 引导扇区, mbr/ebr 所在扇区 */
struct boot_sector {
    uint8_t cord[446];  // 引导代码
    struct partition_table_entry partition_table[4]; //分区表
    uint16_t signature;    // 0x55,0xaa
} __attribute__ ((packed));

static void swap_pairs_bytes(const char* dsk, char* buf, uint32_t len)
{
    uint8_t idx;
    for(idx=0; idx<len; idx+=2) {
        buf[idx+1] = *dsk++;
        buf[idx] = *dsk++;
    }
    buf[idx] = '\0';
}


/* 写device寄存器, 确定硬盘属性: 寻址模式和主/从盘, 以及LBA起始扇区地址23-27bit */
static void select_disk(struct disk* hd, uint32_t lba)
{
    uint8_t reg_dev = BIT_DEV_MBS | BIT_DEV_LBA;
    if(hd->dev_no == 1) {
        reg_dev |= BIT_DEV_DEV;
    }
    reg_dev |= lba >> 24;
    outb(reg_dev(hd->my_channel), reg_dev);
}

/* 写三个LBA寄存器和device寄存器1-3位, 确定LBA 27bit起始扇区地址*/
/* 写sector count寄存器, 确定将要写入或读取扇区数 */
static void select_sector(struct disk* hd, uint32_t lba, uint8_t sec_cnt)
{
    ASSERT(lba + sec_cnt <= max_lba);
    struct ide_channel* channel = hd->my_channel;

    outb(reg_sec_cnt(channel), sec_cnt);
    outb(reg_lba_l(channel), lba);
    outb(reg_lba_m(channel), lba >> 8);
    outb(reg_lba_h(channel), lba >> 16);
    select_disk(hd, lba);
}

/* 写command寄存器, 确定将要执行操作 */
static void cmd_out(struct ide_channel* channel, uint8_t cmd)
{
    /* 开始等待硬盘准备好数据, 向cpu发中断 */
    channel->expecting_intr = true;
    outb(reg_cmd(channel), cmd);
}

/* 从data寄存器中, 读sec_cnt个扇区到buf */
// sec_cnt : 1-256
static void read_from_sector(struct disk* hd, void* buf, uint8_t sec_cnt)
{
    uint32_t size_in_byte;
    if(sec_cnt == 0) {
        size_in_byte = 256 * 512;
    } else {
        size_in_byte = sec_cnt * 512;
    }
    // 一次读取单位字, 一字为两字节. 
    insw(reg_data(hd->my_channel), buf, size_in_byte/2);
}

/* 将buf中 sec_cnt个扇区写入data寄存器中, */
static void write2sector(struct disk* hd, void* buf, uint8_t sec_cnt)
{
    uint32_t size_in_byte;
    if(sec_cnt == 0) {
        size_in_byte = 256*512;
    }else{
        size_in_byte = sec_cnt * 512;
    }
    outsw(reg_data(hd->my_channel), buf, size_in_byte/2);
}

static bool busy_wait(struct disk* hd)
{
    struct ide_channel* channel = hd->my_channel;
    uint16_t time_limit = 30 * 1000;
    while(time_limit -= 10 >= 0) {
        if(!(inb(reg_status(channel)) & BIT_STAT_BSY)) {
            return (inb(reg_status(channel)) & BIT_STAT_DRQ);
        } else {
            mtime_sleep(10);
        }
    }
    return false;
}

/* 获取硬盘参数信息 */
static void identify_disk(struct disk* hd) 
{
    char id_info[512];
    select_disk(hd, 0);
    cmd_out(hd->my_channel, CMD_IDENTIFY);
    sema_down(&hd->my_channel->disk_done);

    if(!busy_wait(hd)) {
         char error[64];
         sprintf(error, "%s identify failed!!!!!!\n", hd->name);
         PANIC(error);
    }

    read_from_sector(hd, id_info, 1);

    char buf[64];
    uint8_t sn_start=10*2, sn_len=20, md_start=27*2, md_len=40;
    swap_pairs_bytes(&id_info[sn_start], buf, sn_len);
    printk("disk %s info:\nSN: %s\n", hd->name, buf);
    memset(buf, 0, sizeof(buf));
    swap_pairs_bytes(&id_info[md_start], buf, md_len);
    printk("MODULE: %s\n", buf);
    uint32_t sectors = *(uint32_t*)&id_info[60*2];
    printk("SECTORS: %d\n", sectors);
    printk("CAPACITY: %dMB\n", sectors*512/1024/1024);
}

/* 从硬盘中, 读sec_cnt个扇区到内存buf */
/*
 * 1. 选择通道, 往该通道的sector count寄存器中, 写入待操作的扇区数
 * 2. 往三个LBA寄存器中写入扇区起始地址的低24bit 
 * 3. 往device寄存器0-3位中, 写入扇区起始地址的24-27位. 并设置剩下4位
 * 4. 往command寄存器中, 写入操作命令
 * 5. 硬盘准备好, 向cpu发中断
 * 6. 读取status寄存器.[向硬盘表示, 此次中断cpu已经处理] 
 * 7. 完成从data寄存器中, 读取数据
 * */
void ide_read(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt)
{
    ASSERT(lba <= max_lba);
    ASSERT(sec_cnt > 0);
    lock_acquire(&hd->my_channel->ide_lock);
    select_disk(hd, lba);

    uint32_t secs_op;
    uint32_t secs_done = 0;
    while(secs_done < sec_cnt) {
        if((secs_done + 256) <= sec_cnt) {
            secs_op = 256;
        } else {
            secs_op = sec_cnt - secs_done;
        }

        select_sector(hd, lba+secs_done, secs_op);
        cmd_out(hd->my_channel, CMD_READ_SECTOR);
        // 阻塞, 等待硬盘准备好, 发送中断
        sema_down(&hd->my_channel->disk_done);

        if(!busy_wait(hd)) {
             char error[64];
             sprintf(error, "%s read sector %d failed!!!!!!\n", hd->name, lba);
             PANIC(error);
        }

        read_from_sector(hd, (void*)((uint32_t)buf + secs_done*512), secs_op);
        secs_done += secs_op;
    }

    lock_release(&hd->my_channel->ide_lock);
}

/* 将内存buf中sec_cnt个扇区写入硬盘 */
void ide_write(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt)
{
    ASSERT(lba <= max_lba);
    ASSERT(sec_cnt > 0);
    lock_acquire(&hd->my_channel->ide_lock);

    select_disk(hd, lba);

    uint32_t secs_op;
    uint32_t secs_done = 0;
    while(secs_done < sec_cnt) {
        if((secs_done + 256) <= sec_cnt) {
            secs_op = 256;
        } else {
            secs_op = sec_cnt - secs_done;
        }

        select_sector(hd, lba+secs_done, secs_op);

        cmd_out(hd->my_channel, CMD_WRITE_SECTOR);

        if(!busy_wait(hd)) { char error[64];
             sprintf(error, "%s write sector %d failed!!!!!!\n", hd->name, lba);
             PANIC(error);
        }

        write2sector(hd, (void*)((uint32_t)buf + secs_done*512), secs_op);

        // 阻塞, 等待硬盘准备好, 发送中断
        sema_down(&hd->my_channel->disk_done);
        secs_done += secs_op;
    }

    lock_release(&hd->my_channel->ide_lock);
}

static void partition_scan(struct disk* hd, uint32_t ext_lba)
{
    struct boot_sector* bs = sys_malloc(sizeof(struct boot_sector));
    ide_read(hd, ext_lba, bs, 1);
    uint8_t part_idx = 0;
    struct partition_table_entry* p = bs->partition_table;

    while(part_idx++ < 4) {
        if(p->fs_type == 0x5) { // 扩展分区
            if(ext_lba_base!=0) {
                partition_scan(hd, p->start_lba+ext_lba_base);
            } else {
                ext_lba_base = p->start_lba;
                partition_scan(hd, p->start_lba);
            }
        } else if(p->fs_type != 0) {  // 有效分区, 其实只有第一分区
            if(ext_lba == 0) {
                hd->prim_parts[p_no].start_lba = ext_lba + p->start_lba;
                hd->prim_parts[p_no].sec_cnt = p->sec_cnt;
                hd->prim_parts[p_no].my_disk = hd;
                list_append(&partition_list, &hd->prim_parts[p_no].part_tag);
                sprintf(hd->prim_parts[p_no].name, "%s%d", hd->name, p_no+1);
                p_no++;
                ASSERT(p_no < 4);
            } else {
                hd->logic_parts[l_no].start_lba = ext_lba + p->start_lba;
                hd->logic_parts[l_no].sec_cnt = p->sec_cnt;
                hd->logic_parts[l_no].my_disk = hd;
                list_append(&partition_list, &hd->logic_parts[l_no].part_tag);
                sprintf(hd->logic_parts[l_no].name, "%s%d", hd->name, l_no+5);
                l_no++;
                if(l_no>=8)
                    return ;
            }
        }
        p++;
    }
    sys_free(bs);
}

static bool partition_info(struct list_elem* pelem, int arg UNUSED)
{
    struct partition* part = elem2entry(struct partition, part_tag, pelem);
    printk("%s start_lba:0x%x, sec_cnt:0x%x\n", part->name, part->start_lba, part->sec_cnt);
    return false;
}

void intr_hd_handler(uint8_t irq_no)
{
    ASSERT(irq_no == 0x2e || irq_no == 0x2f);
    uint8_t ch_no = irq_no - 0x2e;
    struct ide_channel* channel = &channels[ch_no];
    ASSERT(channel->irq_no == irq_no);

    if(channel->expecting_intr) {
        channel->expecting_intr = false;
        sema_up(&channel->disk_done);

        inb(reg_status(channel));
    }
}

void ide_init()
{
    printk("ide_init start\n");
    uint8_t hd_cnt = *((uint8_t*)(0x475));
    printk("   ide_init hd_cnt:%d\n",hd_cnt);
    ASSERT(hd_cnt > 0);
    list_init(&partition_list);
    channel_cnt = DIV_ROUND_UP(hd_cnt, 2);
    struct ide_channel* channel;
    uint8_t channel_no = 0, dev_no=0;

    while(channel_no < channel_cnt) {
        channel = &channels[channel_no];
        sprintf(channel->name, "ide%d", channel_no);
        switch(channel_no) {
            case 0:
                channel->port_base = 0x1f0;
                channel->irq_no = 0x20 + 14;
                break;
            case 1:
                channel->port_base = 0x170;
                channel->irq_no = 0x20 + 15;
                break;
        }
        channel->expecting_intr = false;
        lock_init(&channel->ide_lock);

        sema_init(&channel->disk_done, 0);

        register_handler(channel->irq_no, intr_hd_handler);

        while(dev_no < 2) {
            struct disk* hd = &channel->device[dev_no];
            hd->my_channel = channel;
            hd->dev_no = dev_no;
            sprintf(hd->name, "sd%c", 'a'+channel_no*2 + dev_no);
            identify_disk(hd);
            if(dev_no!=0) {
                partition_scan(hd, 0);
            }
            p_no = 0, l_no = 0;
            dev_no++;
        }
        dev_no = 0;
        channel_no++;
    }

    printk("\n all partition info\n");
    list_traversal(&partition_list, partition_info, (int)NULL);
    printk("ide_init done\n");
}
